package top.hongsheng9527.bookdownload.core.crawl;

import cn.hutool.core.date.DateUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import top.hongsheng9527.bookdownload.utils.*;
import top.hongsheng9527.bookdownload.vo.Book;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.compile;

/**
 * @author Wanghs
 * @create 2022/4/8
 * @description 爬虫解析器
 */
@Slf4j
public class CrawlParser {

    private static final RestTemplate restTemplate = RestTemplateUtil.getInstance("utf-8");

    private static final ThreadLocal<Integer> retryCount = new ThreadLocal<>();

    private static final long RETRY_DOWNLOAD_HTML_TIME = 1000;
    private static final long DOWNLOAD_INTERVAL_TIME = 500;

    @SneakyThrows
    public static void parseBook(RuleBean ruleBean, String bookId, CrawlBookHandler handler) {
        Book book = new Book();
        String bookDetailUrl = ruleBean.getBookDetailUrl().replace("{bookId}", bookId);
        String bookDetailHtml = getByHttpClientWithChrome(bookDetailUrl);
        while (StringUtils.isEmpty(bookDetailHtml)) {
            try {
                Thread.sleep(RETRY_DOWNLOAD_HTML_TIME);

            } catch (InterruptedException e) {
                log.error("sleep失败。。。");
            }
            bookDetailHtml = getByHttpClientWithChrome(bookDetailUrl);
        }
        if (bookDetailHtml != null) {
            Pattern bookNamePatten = compile(ruleBean.getBookNamePatten());
            Matcher bookNameMatch = bookNamePatten.matcher(bookDetailHtml);
            boolean isFindBookName = bookNameMatch.find();
            if (isFindBookName) {
                String bookName = bookNameMatch.group(1);
                //设置小说名
                book.setBookName(bookName);
                Pattern authorNamePatten = compile(ruleBean.getAuthorNamePatten());
                Matcher authorNameMatch = authorNamePatten.matcher(bookDetailHtml);
                boolean isFindAuthorName = authorNameMatch.find();
                if (isFindAuthorName) {
                    String authorName = authorNameMatch.group(1);
                    //设置作者名
                    book.setAuthorName(authorName);
                }
            }
        }
        handler.handle(book);
    }

    public static void parseBookIndexAndContent(String sourceBookId, Book book, RuleBean ruleBean, String SAVE_PATH) {
        //读取目录
        String indexListUrl = ruleBean.getBookIndexUrl().replace("{bookId}", sourceBookId);
        String indexListHtml = getByHttpClientWithChrome(indexListUrl);
        while (StringUtils.isEmpty(indexListHtml)) {
            try {
                Thread.sleep(RETRY_DOWNLOAD_HTML_TIME);
            } catch (InterruptedException e) {
                log.error("sleep失败。。。");
            }
            indexListHtml = getByHttpClientWithChrome(indexListUrl);
        }
        if (indexListHtml != null) {
            if (StringUtils.isNotBlank(ruleBean.getBookIndexStart())) {
                indexListHtml = indexListHtml.substring(indexListHtml.indexOf(ruleBean.getBookIndexStart()) + ruleBean.getBookIndexStart().length());
            }
            if (book.getCrawlSourceId() == 18) {
                // 目录分页情况
                String resListHtml = handleNextCatalogue(ruleBean, indexListHtml);
                indexListHtml = resListHtml;
                System.out.println(indexListHtml);
            }
            if (book.getCrawlSourceId() == 17) {
                String flag = "<ul class=\"chaw_c\" id=\"chapterList\">";
                indexListHtml = indexListHtml.substring(indexListHtml.indexOf(flag) + flag.length());
            }
            Pattern indexIdPatten = compile(ruleBean.getIndexIdPatten());
            Matcher indexIdMatch = indexIdPatten.matcher(indexListHtml);
            Pattern indexNamePatten = compile(ruleBean.getIndexNamePatten());
            Matcher indexNameMatch = indexNamePatten.matcher(indexListHtml);
            boolean isFindIndex = indexIdMatch.find() & indexNameMatch.find();
            String baseDir = SAVE_PATH + DateUtil.now().substring(0, 10) + "-" + Thread.currentThread().getName();
            File file = new File(baseDir + "\\" + book.getBookName() + ".txt");
            if (file.exists() && file.isFile()) {
                file.delete();
            }
            while (isFindIndex) {
                try {
                    Thread.sleep(DOWNLOAD_INTERVAL_TIME);
                } catch (InterruptedException e) {
                    log.error("sleep失败。。。");
                }
                String indexName = indexNameMatch.group(1);
                String sourceIndexId = indexIdMatch.group(1);
                String bookContentUrl = ruleBean.getBookContentUrl();
                int calStart = bookContentUrl.indexOf("{cal_");
                if (calStart != -1) {
                    //内容页URL需要进行计算才能得到
                    String calStr = bookContentUrl.substring(calStart, calStart + bookContentUrl.substring(calStart).indexOf("}"));
                    String[] calArr = calStr.split("_");
                    int calType = Integer.parseInt(calArr[1]);
                    if (calType == 1) {
                        ///{cal_1_1_3}_{bookId}/{indexId}.html
                        //第一种计算规则，去除第x个参数的最后y个字母
                        int x = Integer.parseInt(calArr[2]);
                        int y = Integer.parseInt(calArr[3]);
                        String calResult;
                        if (x == 1) {
                            calResult = sourceBookId.substring(0, sourceBookId.length() - y);
                        } else {
                            calResult = sourceIndexId.substring(0, sourceBookId.length() - y);
                        }
                        if (calResult.length() == 0) {
                            calResult = "0";
                        }
                        bookContentUrl = bookContentUrl.replace(calStr + "}", calResult);
                    }
                }
                String contentUrl = bookContentUrl.replace("{bookId}", sourceBookId).replace("{indexId}", sourceIndexId);
                if (book.getCrawlSourceId() == 18) {
                    // 万古神帝笔趣阁的章节内容url
                    contentUrl = ruleBean.getPicUrlPrefix() + sourceIndexId;
                }
                //查询章节内容
                String contentHtml = "";
                switch (book.getCrawlSourceId()) {
                    case 166: // i笔趣阁
                        break;
                    case 17: // https://www.52biqv.com/
                        try {
                            contentHtml = getByHttpClientWithChrome(contentUrl);
                            while (StringUtils.isEmpty(contentHtml)) {
                                Thread.sleep(RETRY_DOWNLOAD_HTML_TIME);
                                contentHtml = getByHttpClientWithChrome(contentUrl);
                            }
                        } catch (Exception e) {
                            log.error("sleep失败。。。");
                        }
                        if (contentHtml != null && !contentHtml.contains("正在手打中")) {
                            contentHtml = handleNextPage(contentHtml, ruleBean, indexName, false, baseDir, file);
                            int nextPageNum = 2;
                            while (contentHtml.contains("\">（继续下一页）</p>")) {
                                String prefixUrl = contentUrl.substring(0, contentUrl.length() - 5);
                                String newUrl = prefixUrl + "_" + nextPageNum + ".html";
                                try {
                                    contentHtml = getByHttpClientWithChrome(newUrl);
                                    while (StringUtils.isEmpty(contentHtml)) {
                                        Thread.sleep(RETRY_DOWNLOAD_HTML_TIME);
                                        contentHtml = getByHttpClientWithChrome(newUrl);
                                    }
                                } catch (Exception e) {
                                    log.error("sleep失败。。。");
                                }
                                if (contentHtml != null && !contentHtml.contains("正在手打中")) {
                                    contentHtml = handleNextPage(contentHtml, ruleBean, null, false, baseDir, file);
                                }
                                nextPageNum++;
                            }
                            //处理一章的最后一页
                            if (contentHtml != null && !contentHtml.contains("正在手打中")) {
                                handleNextPage(contentHtml, ruleBean, null, true, baseDir, file);
                            }
                        }

                        break;
                    case 18:
                        try {
                            contentHtml = getByHttpClientWithChrome(contentUrl);
                            while (StringUtils.isEmpty(contentHtml)) {
                                Thread.sleep(RETRY_DOWNLOAD_HTML_TIME);
                                contentHtml = getByHttpClientWithChrome(contentUrl);
                            }
                        } catch (Exception e) {
                            log.error("sleep失败。。。");
                        }
                        if (contentHtml != null) {
                            contentHtml = handleNextPage1(contentHtml, ruleBean, indexName, false, baseDir, file);
                            int nextPageNum = 2;
                            while (contentHtml.contains("本章未完，点击[ 下一页 ]继续阅读")) {
                                String prefixUrl = contentUrl.substring(0, contentUrl.length() - 5);
                                String newUrl = prefixUrl + "_" + nextPageNum + ".html";
                                try {
                                    contentHtml = getByHttpClientWithChrome(newUrl);
                                    while (StringUtils.isEmpty(contentHtml)) {
                                        Thread.sleep(RETRY_DOWNLOAD_HTML_TIME);
                                        contentHtml = getByHttpClientWithChrome(newUrl);
                                    }
                                } catch (Exception e) {
                                    log.error("sleep失败。。。");
                                }
                                if (contentHtml != null) {
                                    contentHtml = handleNextPage1(contentHtml, ruleBean, null, false, baseDir, file);
                                }
                                nextPageNum++;
                            }
                            //处理一章的最后一页
                            if (contentHtml != null) {
                                handleNextPage1(contentHtml, ruleBean, null, true, baseDir, file);
                            }
                        }


                        break;
                    default:
                        try {
                            contentHtml = getByHttpClientWithChrome(contentUrl);
                            while (StringUtils.isEmpty(contentHtml)) {
                                Thread.sleep(RETRY_DOWNLOAD_HTML_TIME);
                                contentHtml = getByHttpClientWithChrome(contentUrl);
                            }
                            if (contentHtml != null && !contentHtml.contains("正在手打中")) {
                                String content = contentHtml.substring(contentHtml.indexOf(ruleBean.getContentStart()) + ruleBean.getContentStart().length());
                                content = content.substring(0, content.indexOf(ruleBean.getContentEnd()));
                                save2File(baseDir, file, indexName, content);
                            }
                        } catch (Exception e) {
                            log.error("sleep失败。。。");
                        }
                }

                isFindIndex = indexIdMatch.find() & indexNameMatch.find();
            }
            //完成任务，复制到finish目录下
            try {
                File dir = new File(SAVE_PATH + "下载完成\\");
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                FileUtils.copyFile(file, new File(SAVE_PATH + "下载完成\\" + book.getBookName() + "-finish.txt"));
                //copy完成后删除原先目录和文件
                deleteDir(new File(baseDir));
            } catch (IOException e) {
                log.error("复制文件出现异常。。。");
            }
        }
    }

    private static String handleNextCatalogue(RuleBean ruleBean, String indexListHtml) {
        if (StringUtils.isNotBlank(ruleBean.getBookIndexStart()) && indexListHtml.contains(ruleBean.getBookIndexStart())) {
            indexListHtml = indexListHtml.substring(indexListHtml.indexOf(ruleBean.getBookIndexStart()) + ruleBean.getBookIndexStart().length());
        }
        String resHtml = indexListHtml;
        int index = 1;
        log.info("遍历目录第{}页", index);
        while (resHtml.contains(".html'>下页</a>")) {
            index++;
            // 解析出下一页的url
            Pattern patten = compile("<a\\s+class='nextPage'\\s+href='(/[a-z]+/[a-z]+_\\d+\\.html)'>[^/]+</a>");
            Matcher match = patten.matcher(resHtml);
            boolean find = match.find();
            // 获取对应html字符串
            String suffixUrl = "";
            if (find) {
                suffixUrl = match.group(1);
                log.info(suffixUrl);
            }
            String picUrlPrefix = ruleBean.getPicUrlPrefix();
            String nextListHtml = "";
            try {
                Thread.sleep(DOWNLOAD_INTERVAL_TIME);
                nextListHtml = getByHttpClientWithChrome(picUrlPrefix + suffixUrl);
                while (StringUtils.isEmpty(nextListHtml)) {
                    Thread.sleep(RETRY_DOWNLOAD_HTML_TIME);
                    nextListHtml = getByHttpClientWithChrome(picUrlPrefix + suffixUrl);
                }
                log.info("遍历目录第{}页", index);
            } catch (Exception e) {
                log.error("sleep失败。。。");
            }
            // 去掉下一页的开头 TODO:可以改为放到数据库字段里
            String start = "<div class=\"directoryArea\">";
            nextListHtml = nextListHtml.substring(nextListHtml.indexOf(start) + start.length());
            // 去掉resHtml的结尾
//            if (resHtml.contains(".html'>下页</a>")) {
//                resHtml = resHtml.substring(0, resHtml.indexOf(".html'>下页</a>"));
//            }
            if (resHtml.contains("<div class=\"pagelistbox\">")) {
                resHtml = resHtml.substring(0, resHtml.indexOf("<div class=\"pagelistbox\">"));
            }
            // 拼接
            resHtml = resHtml + nextListHtml;
        }
        return resHtml;
    }

    private static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            //递归删除目录中的子目录下
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空，可以删除
        return dir.delete();
    }

    public static String handleNextPage(String contentHtml, RuleBean ruleBean, String indexName, boolean lastPage, String baseDir, File file) {
        int i = contentHtml.indexOf(ruleBean.getContentStart());
        int length = ruleBean.getContentStart().length();
        String content = contentHtml.substring(i + length);
        int i1 = content.indexOf(ruleBean.getContentEnd());
        content = content.substring(0, i1);
        content = content.replace("\n</dt>\n", "");
        if (!lastPage) {
            content = content.replace("<p style=\"font-weight: 400;color:#af888c;\">（继续下一页）</p>\n", "").replace("<p style=\"font-weight: 400;color:#af888c;\">（如异常请退出阅读/畅读模式）</p>\n", "");
            int i2 = content.lastIndexOf("</p>");
            content = content.substring(0, i2);
        }
        content = content.replace("<p>笔趣阁 ", "");
        save2File(baseDir, file, indexName, content);
        return contentHtml;
    }

    public static String handleNextPage1(String contentHtml, RuleBean ruleBean, String indexName, boolean lastPage, String baseDir, File file) {
        int i = contentHtml.indexOf("<div id=\"nr1\">");
        int length = "<div id=\"nr1\">".length();
        String content = contentHtml.substring(i + length);
        int i1 = content.indexOf("<script>style_middle()</script>");
        content = content.substring(0, i1);
        content = content.replace("\n</dt>\n", "");
        content = content.replaceAll("<br /><br />　　", "\n").replaceAll("</div>", "");
        if (!lastPage) {
            content = content.replaceAll("<font color=\"blue\">本章未完，点击[ 下一页 ]继续阅读-->></font>", "");
        }
        save2File(baseDir, file, indexName, content);
        return contentHtml;
    }

    private static void save2File(String baseDir, File f, String indexName, String content) {
        try {
            File dir = new File(baseDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            log.info("\n" + f.getAbsolutePath() + " " + indexName);
            f.createNewFile();
            FileWriter fileWriter = new FileWriter(f, true);
            PrintWriter pw = new PrintWriter(fileWriter);
            String replace = content.replace(" ", "")
                    .replace("<p>", "")
                    .replace("</p>", "\n\n");
            String replace1 = replace.replace("&nbsp;", "");
            String replace2 = replace1.replace("<br/><br/>", "\n\n");
            if (!StringUtils.isEmpty(indexName)) {
                pw.println(indexName + "\n");
            }
            pw.println(replace2 + "\n\n");
            pw.flush();
            fileWriter.flush();
            pw.close();
            fileWriter.close();
//            Thread.sleep(500);
        } catch (Exception e) {
            log.error("存入txt出现异常。。。");
        }
    }

    private static String getByHttpClient(String url) {
        try {
            ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
            if (forEntity.getStatusCode() == HttpStatus.OK) {
                String body = forEntity.getBody();
                assert body != null;
                if (body.length() < Constants.INVALID_HTML_LENGTH) {
                    return processErrorHttpResult(url);
                }
                //成功获得html内容
                return body;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return processErrorHttpResult(url);
    }

    private static String getByHttpClientWithChrome(String url) {
        try {
            String body = HttpUtil.getByHttpClientWithChrome(url);
            if (body != null && body.length() < Constants.INVALID_HTML_LENGTH) {
                return processErrorHttpResult(url);
            }
            //成功获得html内容
            return body;
        } catch (Exception e) {
            log.error("获取html失败。。。");
        }
        return processErrorHttpResult(url);
    }

    @SneakyThrows
    private static String processErrorHttpResult(String url) {
        Integer count = retryCount.get();
        if (count == null) {
            count = 0;
        }
        if (count < Constants.HTTP_FAIL_RETRY_COUNT) {
            Thread.sleep(new Random().nextInt(10 * 1000));
            retryCount.set(++count);
            return getByHttpClient(url);
        }
        return null;
    }
}
